package com.yyge;

import com.yyge.handler.NettyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 初始化netty
 */
@Slf4j
@Data
@Component
public class NettyInit implements ApplicationRunner {

    private Map<String, ChannelHandlerContext> map =new HashMap<>();

    //这里是一个测试 如果你需要从外部注入类似于redisTemplate这类服务
    //则可以在new NettyServerHandler 的时候将对象注入 否则在内部取到的是空值
    //因为 NettyServerHandler 每一次连接客户端都会new一个新的对象
    public void initNetty(){
        log.info("Netty通知组件正在初始化...");
        //log.info("TRC20转账==> fromAddress:{}, toAddress:{},price:{}", fromAddress, toAddress, amount);
        //创建两个现场组bossGroup 和 workerGroup,含有的子线程NioEventLoop的个数默认为CPU的核数的两倍
        //一主多从  一般主selector是一个 也可以多个 建议是一个（老板一个 员工多个...）
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);  //内部线程池 - 主
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(10); //内部线程池 - 从
        try{
            //创建服务端的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();

            //使用链式编程来配置参数
            bootstrap.group(bossGroup,workerGroup) //设置两个线程组

                    //使用NioServerSocketChannel 作为服务器的通道实现
                    .channel(NioServerSocketChannel.class)

                    //初始化服务器连接队列大小,服务器处理客户端连接请求是顺序处理的，所以同一件只能处理一个客户端连接
                    // 多个客户端同时来的时候，服务端讲不能处理的客户端连接请求放在队列中等待处理
                    .option(ChannelOption.SO_BACKLOG,1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                             ChannelPipeline pipeline = socketChannel.pipeline();
                            //加入一个netty 提供 IdleStateHandler
                            /*
                            说明
                            1. IdleStateHandler 是netty 提供的处理空闲状态的处理器
                            2. long readerIdleTime : 表示多长时间没有读（server没有读取客户端数据）, 就会发送一个心跳检测包检测是否连接
                            3. long writerIdleTime : 表示多长时间没有写（server没有发送给客户端数据）, 就会发送一个心跳检测包检测是否连接
                            4. long allIdleTime : 表示多长时间没有读写, 就会发送一个心跳检测包检测是否连接

                            5. 当没有读、写或者全部读写，会触发一个IdleStateEvent事件
                            6. 当 IdleStateEvent 触发后 , 就会传递给管道 的下一个handler去处理
                                通过调用(触发)下一个handler 的 userEventTiggered , 在该方法中去处理 IdleStateEvent(读空闲，写空闲，读写空闲)
                             */
                            pipeline.addLast(new IdleStateHandler(30,30,30, TimeUnit.SECONDS));
                            //向pipeline加入解码器
                            pipeline.addLast("decoder", new StringDecoder(StandardCharsets.UTF_8));
                            //向pipeline加入编码器
                            pipeline.addLast("encoder", new StringEncoder(StandardCharsets.UTF_8));
                            //加入一个对空闲检测进一步处理的handler(自定义)
                            pipeline.addLast(new NettyServerHandler());

                            // 这里建议直接new 不要使用@Autowre注入 每一次连接都会调用 ！！！！！
                            //socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
                   //绑定一个端口号并且同步,生成了一个ChannelFuture异步对象,通过isDone()能方法可以片段异步事件的执行情况
                   //启动服务器(绑定端口号) ,bind是异步操作,sync方法是等待异步操作执行完毕
            ChannelFuture channelFuture = bootstrap.bind(1234).sync();
            log.info("Netty通知组件初始化完成 ==> localhost:1234");
            //给cf注册监听器,监听我们关心的事件

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
     System.out.println(1231);
     //初始化netty
      this.initNetty();
    }
}
